前一天,我替 Music Recap 的數字訂好了規則:活動次數可以計算,未知播放時間保留空值,每個結果都要交代用了多少資料。
今天終於可以打開第一份用自己資料產生的 Recap。
這份報告讀入我的 YouTube Music 活動紀錄,列出來源 ID 活動排行、頻道活動排行,以及日期、月份與時段分布。每張表都附上統計依據、資料覆蓋率和份額分母,讓數字能對回原本的資料。
這次使用先前已驗證的 Google Takeout 原始 HTML。執行前先比對檔案,確認與前兩天使用的是同一份資料,再讓它經過匯入器與今天的 Recap 程式。
原檔包含 26,900 張活動卡片。其中 15,403 筆屬於 YouTube Music,另外 11,497 筆一般 YouTube 活動依既有分類規則略過,拒絕與待分類的卡片都是 0。
這 15,403 筆音樂活動,現在產生了以下結果:
| 項目 | 真實資料結果 |
|---|---|
| YouTube Music 活動 | 15,403 筆 |
| 不同來源 Video ID | 1,197 個 |
| 可用頻道標籤 | 409 組 |
| 來源 ID 排行覆蓋率 | 15,403/15,403,100% |
| 頻道排行覆蓋率 | 15,376/15,403,99.82% |
| 日期、月份與時段統計覆蓋率 | 15,403/15,403,100% |
| 實際出現活動的日期 | 198 天 |
| 逐筆播放時間未知 | 15,403/15,403 |
這裡的覆蓋率,分母都是這次輸入的音樂活動。100% 代表這批紀錄都能參與該項統計,不能據此推論帳號的全部收聽歷史都已取得。
來源 ID 排行以 Video ID 分組。同一個 Video ID 出現在多少筆活動裡,就累積多少次。
這批資料的第一名來源項目有 106 次活動,占 15,403 筆可納入活動的約 0.69%。前十列來源項目合計 1,017 次,占約 6.60%;其他來源項目合計 14,386 次。
這些數字描述的是匯出紀錄中的活動次數。106 次活動無法直接換成完整播放 106 次,也無法推算播放分鐘數。
有 Video ID、卻缺少可用標題的紀錄,仍能參與排行。報告會顯示「標題未知」,不會因為名稱缺失而丟掉它。若同一個 Video ID 有多種原始標題,程式使用最常出現的可用標題作為顯示名稱。
另一方面,兩個 Video ID 即使標題相同,目前仍分開保留。不同版本是否代表同一首歌曲,要等後面的歌曲身分辨識處理。這也是報告目前使用「來源 ID 活動排行」這個名稱的原因。
次數相同時,報告會保留並列名次,並以來源 ID 穩定排序。重跑同一批事件,結果不會因為輸入順序而換位置。
這批音樂活動並非每筆都有可用頻道名稱。
原始頻道缺值共有 114 筆。既有匯入器利用相同 Video ID 的唯一頻道證據補回 87 筆,剩下 27 筆仍保留未知。因此,頻道排行能使用的活動數是 15,376 筆。
這時,排行裡有兩個需要分開的比例。
第一個是某個頻道的活動份額。這次第一名頻道標籤累積 6,116 筆活動,所以它在可納入頻道排行的活動中占:
6,116 ÷ 15,376 ≈ 39.78%
第二個是整份頻道排行的資料覆蓋率:
15,376 ÷ 15,403 ≈ 99.82%
39.78% 描述該頻道在榜內的份額;99.82% 描述這張榜使用了多少輸入資料。兩個分母回答的問題不同,所以程式分開保存。
只顯示前十名時,也不會把前十名的份額重新湊成 100%。這次頻道榜前十列合計 13,559 筆,約占可納入活動的 88.18%;其他可用頻道合計 1,817 筆。另外 27 筆頻道缺值則清楚列為未納入。
目前的頻道分組沿用來源中的精確標籤。帶有 Topic 的名稱與一般頻道名稱會分開,尚未把它們歸成同一位歌手。
時間分布沿用這份匯出的明確解析設定:把來源中的 CST 解釋為 +08:00,報表也使用 +08:00。
在這個設定下,這批音樂活動的首末時間是 2026 年 1 月 31 日 22:33:30 到 9 月 15 日 18:52:27。
月份分布如下。統計依據都是活動次數,每列份額的分母為 15,403 筆,時間欄位覆蓋率為 100%。
| 月份 | 活動數 |
|---|---|
| 2026 年 1 月 | 21 |
| 2026 年 2 月 | 2,472 |
| 2026 年 3 月 | 1,997 |
| 2026 年 4 月 | 1,539 |
| 2026 年 5 月 | 1,336 |
| 2026 年 6 月 | 1,119 |
| 2026 年 7 月 | 3,454 |
| 2026 年 8 月 | 2,139 |
| 2026 年 9 月 | 1,326 |
在這份資料裡,7 月的活動最多,約占全部音樂活動的 22.42%。1 月與 9 月只包含這批資料首末端的部分日期,因此不能把它們當成完整月份,直接解讀為聆聽量下降。
每小時分布也已產生。其中 00:00 到 00:59 累積 1,278 筆活動,約占 8.30%,是這批資料中活動最多的一個小時區間。
這表示紀錄中的事件集中在哪些時段,還不能推論每個時段實際播放了多久。日期分布列出有活動的 198 天,沒有紀錄的日期也不會被直接解釋成完全沒聽音樂。
這次所有 YouTube Music 事件的 played_ms 都維持 null。
所以,逐筆觀測播放時間與完整觀測播放時間都標示為無法提供。程式沒有用歌曲長度,也沒有用兩筆事件的間隔去填補秒數。
另外提供的官方 Recap 總分鐘數目前也尚未取得,這個欄位同樣保留無法提供。之後取得時,會保存它自己的期間,不分攤到每筆事件。
活動報告仍然能回答來源項目、頻道和時間分布的問題,同時讓缺少的資料留在讀者看得到的位置。
我沒有只停在程式成功輸出檔案。
第一層驗收從同一份原始 HTML 開始,經過嚴格匯入,再讓全部 15,403 筆真實事件進入 Recap。驗收腳本的 14 項檢查全數通過,包括事件數、來源 ID 數量、欄位覆蓋率、首末時間、未知時長與各表加總。
第二層另外使用不同的 HTML 解析方式讀取原始檔,重新計算來源分類、Video ID、可用頻道、日期、月份和小時分布,再與新報告對照。
這次獨立核對涵蓋來源與頻道榜各十列、全部 198 個日期分組、9 個月份與 24 個小時分組,也核對各表完整組數、可用活動總數、份額與被省略的活動數。30 項核對全部相符。
先前同一版功能已通過合成資料端到端流程與既有回歸測試;這次補上的,則是原先缺少的真實 Recap 執行證據。
今天由 ChatGPT 接續前一天的 metric contract,把排行、份額、coverage 與時間分布真正接到 Recap 輸出。
過程中也檢查了幾個很容易讓結果看起來合理、實際卻算錯的地方。
例如只顯示前十名時,不能把前十名重新湊成 100%;頻道缺值不能直接忽略分母差異;Video ID 名稱相似也不能直接合併;時間分布還要先套用正確的時區解釋。
最後再用真實原始檔重新跑完整流程,並透過另一套解析方式重新計算統計數字,確認新產生的 Recap 與原始資料一致。
這次沒有另外啟動 Codex 工作任務。程式、測試與驗收文件持續保存在專案裡,之後 ChatGPT 與 Codex 都能依同一份狀態接手。
今天完成的成果,是從我的真實 YouTube Music 原始資料產生一份可以閱讀的活動報告。
目前已經可以看到來源 ID 活動排行、頻道活動排行、月份分布、日期分布與每小時活動分布,而且每一項結果都保留統計依據和資料覆蓋率。
雖然 YouTube Music Takeout 沒有提供每筆實際播放秒數,但至少現在這 15,403 筆活動已經真正開始變成一份屬於自己的 Music Recap。
下一天將接上第二個必要資料來源:Spotify。
我要先檢查真實 Spotify 匯出檔到底提供哪些欄位、它的時間代表什麼,以及 msPlayed 是否真的能讓我們補上 YouTube Music 缺少的播放時間資訊。
把榜內份額與資料覆蓋率分開,還特別標出 1 月、9 月是部分月份,這讓 Recap 不會把漂亮數字講過頭。接上 Spotify 後,跨平台同曲辨識會怎麼驗收?你會優先用 ISRC,還是名稱、藝人與時長的組合規則?